package com.chenu.gamscriptman.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.chenu.gamscriptman.compoent.Global;
import com.chenu.gamscriptman.dao.DeviceDao;
import com.chenu.gamscriptman.dao.GameTaskDao;
import com.chenu.gamscriptman.dao.UserDao;
import com.chenu.gamscriptman.dao.UserDataDao;
import com.chenu.gamscriptman.domain.Device;
import com.chenu.gamscriptman.domain.GameTask;
import com.chenu.gamscriptman.domain.User;
import com.chenu.gamscriptman.domain.noindb.*;
import com.chenu.gamscriptman.service.DeviceTask;
import com.chenu.gamscriptman.service.NotifyService;
import com.chenu.gamscriptman.utils.ExcelUtils;
import com.chenu.gamscriptman.utils.TimeUtils;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.StringUtils;

import java.util.*;

import static com.chenu.gamscriptman.domain.Device.groupNrs;
import static com.chenu.gamscriptman.domain.Device.noGroupNr;

/**
 * @author flyan
 * date 2019-12-27
 * @function 设备任务
 */
@CacheConfig(cacheNames = "device")   /* 本类使用缓存名称：device */
@Service("deviceTask")
public class DeviceTaskImpl implements DeviceTask {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /* 请求设备服务的服务路由键 */
    public static final String SERVICE_ROUTING_KEY = "device";

    /* 消息服务器的设备消息队列 */
    public static final String QUEUE = "gamscriptman.devicetask.queue";

    /* 数据库数据是否被更新了 */
    public static boolean DbIsUpdate = true;

    /* redis模板，用于缓存和处理结果的暂存 */
    @Autowired
    private RedisTemplate jsonRedisTemplate;

    /* gson，用于处理json数据 */
    @Autowired
    private Gson gson;
    private Gson deviceGson = new GsonBuilder()

            .setExclusionStrategies(new ExclusionStrategy() {

                /* 设置需要忽略的字段 */
                @Override
                public boolean shouldSkipField(FieldAttributes f) {
                    return Device.ignoreAttr.contains(f.getName());
                }

                /* 设置需要清空的字段类型 */
                @Override
                public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                }
            }).create();

    /* 其他任务 */
    @Autowired
    NotifyService notifyService;

    /* 事务管理器 */
    @Autowired
    DataSourceTransactionManager transactionManager;

    /* dao */
    @Autowired
    DeviceDao deviceDao;
    @Autowired
    UserDao userDao;
    @Autowired
    GameTaskDao gameTaskDao;
    @Autowired
    UserDataDao userDataDao;

    /* 任务能提供的所有功能索引号 */
    public static final int DEVICE_LIST = 0;                /* 得到所有设备信息 */
    public static final int DEVICE_ADD_OR_EDIT_VIEW = 1;    /* 前往设备添加/编辑页面 */
    public static final int DEVICE_ADD_OR_UPDATE = 2;       /* 设备添加或更新 */
    public static final int DEVICE_REMOVE = 3;              /* 设备删除 */
    public static final int DEVICE_COUNT = 4;               /* 得到设备数量 */
    public static final int DEVICE_GET = 5;                 /* 得到一条设备信息，可通过设备id/设备号/MAC地址 */
    public static final int DEVICE_EXPORT_FILE = 6;         /* 导出结果为文件 */
    public static final int DEVICE_WEEK_STATISTICS = 7;     /* 得到一周的设备统计信息 */


    /* 任务入口，等待一条消息并处理，然后回发一条包含处理结果的消息 */
    @Override
    @RabbitListener(queues = QUEUE)
    public Message task(Message message) {
        Message rs = null;  /* 就是一个指针 */

        /* 看看用户想做什么？ */
        switch (message.getType()){
            case DEVICE_LIST:                 /* 列出所有设备 */
                logger.debug("设备任务：DEVICE_LIST...");
                rs = do_list(message);
                break;

            case DEVICE_ADD_OR_EDIT_VIEW:     /* 需要前往设备添加或编辑页面 */
                logger.debug("设备任务：DEVICE_ADD_OR_EDIT_VIEW...");
                rs = do_no_call(message);       /* 设备不允许在页面上添加，不提供这个功能 */
                break;

            case DEVICE_ADD_OR_UPDATE:        /* 添加或更新一个设备 */
                logger.debug("设备任务：DEVICE_ADD_OR_UPDATE...");
                rs = do_add_or_update(message);
                break;

            case DEVICE_REMOVE:               /* 删除一个设备 */
                logger.debug("设备任务：DEVICE_REMOVE...");
                rs = do_remove(message);
                break;

            case DEVICE_COUNT:               /* 得到设备数量 */
                logger.debug("设备任务：DEVICE_COUNT...");
                rs = do_count(message);
                break;

            case DEVICE_GET:                    /* 得到一条设备信息 */
                logger.debug("设备任务：DEVICE_GET...");
                rs = do_get(message);
                break;

            case DEVICE_EXPORT_FILE:
                logger.debug("设备任务：DEVICE_EXPORT_FILE...");
                rs = do_export_file(message);
                break;

            case DEVICE_WEEK_STATISTICS:
                logger.debug("设备任务：DEVICE_WEEK_STATISTICS...");
                rs = do_week_statistics(message);
                break;

            default:
                logger.warn("设备任务：你正在请求一个不存在的功能服务...");
                rs = do_no_call(message);
                break;
        }

        return rs;
    }

    private Message do_week_statistics(Message message) {
        /* 它很简单，直接返回统计信息即可(json) */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.DEVICE_TASK,
                Message.HANDLER_SUCCESS,
                gson.toJson(WeekNrUpdateDevice)
        );
    }

    private Message do_export_file(Message message) {
        /* 得到数据，它们在缓存中！ */
        PageBean<Device> data = (PageBean<Device>) jsonRedisTemplate.opsForValue().get(Global.REDIS_DEVICE_PAGES);

        /* 设置Excel文件标题 */
        List<String> title = new ArrayList<>();
        title.add("设备号");
        title.add("设备名");
        title.add("系统版本");
        title.add("MAC地址");
        title.add("存储空间");
        title.add("内网IP");
        title.add("外网IP");
        title.add("电池信息");
        title.add("网络类型");
        title.add("内存空间");
        title.add("uuid");
        title.add("IMEI");
        title.add("序列号");
        title.add("型号");
        title.add("相同IP数量");
        title.add("运行游戏");
        title.add("扩展字符串");
        title.add("扩展数值");
        title.add("更新时间");
        title.add("管理员");
        /* 设置列顺序 */
        List<String> keyList = new ArrayList<>();
        keyList.add("sdevNr");
        keyList.add("name");
        keyList.add("sysVersion");
        keyList.add("mac");
        keyList.add("storage");
        keyList.add("intranetIp");
        keyList.add("extranetIp");
        keyList.add("battery");
        keyList.add("netType");
        keyList.add("memory");
        keyList.add("uuid");
        keyList.add("imei");
        keyList.add("serialNr");
        keyList.add("model");
        keyList.add("nrSameIp");
        keyList.add("runGame");
        keyList.add("extenString");
        keyList.add("extenInt");
        keyList.add("uptime");
        keyList.add("admin.username");
        /* 设置内容 */
        List<Map<String, Object>> context = new ArrayList<>();
        Map<String, Object> line;
        for (Device device : data.getRecords()){
            line = new HashMap<>();
            line.put("sdevNr", (device.getGroupNr().equals(noGroupNr) ? "" : device.getGroupNr()) + device.getDevNr());
            line.put("name", device.getName());
            line.put("sysVersion", device.getSysVersion());
            line.put("mac", device.getMac());
            line.put("storage", device.getStorage());
            line.put("intranetIp", device.getIntranetIp());
            line.put("extranetIp", device.getExtranetIp());
            line.put("battery", device.getBattery());
            line.put("netType", device.getNetType());
            line.put("memory", device.getMemory());
            line.put("uuid", device.getMemory());
            line.put("imei", device.getImei());
            line.put("serialNr", device.getSerialNr());
            line.put("model", device.getModel());
            line.put("nrSameIp", device.getNrSameIp());
            line.put("runGame", device.getRunGame());
            line.put("extenString", device.getExtenString());
            line.put("extenInt", device.getExtenInt());
            line.put("uptime", TimeUtils.unixStamp2Date(device.getUptime(), "yyyy-MM-dd hh:mm:ss"));
            line.put("admin.username", device.getAdmin().getUsername());
            context.add(line);
        }

        /* 生成Execl文件 */
        try {
            String excelFilePath = ExcelUtils.createExeclFileByDBList("设备数据", title, keyList, context);
            logger.debug("--> " + excelFilePath + " <--");
            /* 放入文件真实路径 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_DOWNLOAD_FILE_PATH, excelFilePath);
            /* 成功，准备下载文件 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.DEVICE_TASK,
                    Message.HANDLER_SUCCESS,
                    "/download"
            );
        } catch (Exception e) {
            e.printStackTrace();
            /* 失败 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.DEVICE_TASK,
                    Message.HANDLER_FAILURE,
                    "设备任务：生成Excel文件失败，请重试..."
            );
        }
    }

    private Message do_list(Message message) {
        /* 它很简单，根据条件和分页查询设备列表 */
        PageBean<Device> data = null;
        Statistics statistics = null;   /* 设备统计信息 */
        String rtData = null;

        /* 解析消息中的数据 */
        ListMsgParameter<Device> parameter = gson.fromJson(message.getData(), new TypeToken<ListMsgParameter<Device>>() {}.getType());
        if(parameter != null && parameter.getPageBean() != null){    /* 不为null才需要 */
            parameter.setPageBean(new PageBean<Device>(
                    parameter.getPageBean().getCurrent(),
                    parameter.getPageBean().getSize(),
                    true,
                    parameter.getPageBean().getDynamicHopeSize()
            ));
        }

        /* 检查查询参数 */
        boolean paging = false;     /* 是否需要分页？ */
         if(parameter == null) { /* 没有任何限制，查看设备分组默认不分组 */
            parameter = new ListMsgParameter<Device>()
                    .setPageBean(null)
                    .setQueryBean(new Device().setGroupNr(noGroupNr));
        } else {
            /* 保证分组查询，没有分组号就按查看设备分组默认不分组 */
            if(parameter.getQueryBean() == null){
                parameter.setQueryBean(new Device().setGroupNr(noGroupNr));
            } else if(StringUtils.isEmpty(parameter.getQueryBean().getGroupNr())){
                parameter.setQueryBean(parameter.getQueryBean().setGroupNr(noGroupNr));
            } else {
                /* 看看分组号是否是合法的分组号 */
                boolean pass = false;
                for(String c : groupNrs){
                    if(parameter.getQueryBean().getGroupNr().equals(c)){
                        pass = true;
                    }
                }
                if(!pass){
                    /* 不合法，那么设置分组号为不分组 */
                    parameter.getQueryBean().setGroupNr(noGroupNr);
                }
            }
            paging = true;
        }

        /* 检查是否可以使用更快速的缓存 */
        Device queryBean = (Device) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_DEVICE);
        PageBean<Device> pageBean = (PageBean<Device>)jsonRedisTemplate.opsForValue().get(Global.REDIS_DEVICE_PAGES);
        if(new ListMsgParameter<Device>()
                .setEx_int(null)
                .setQueryBean(queryBean)
                .setPageBean(pageBean)
                .equals(parameter) && !DbIsUpdate){
            logger.debug("设备列表 --> 使用缓存");
            /* 查询条件一样，而且数据库近期没更新，那么不需要去数据库拿了，缓存中有 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.DEVICE_TASK,
                    Message.HANDLER_SUCCESS,
                    "/devices"
            );
        }
        logger.debug("设备列表 --> 数据库查询");

        /* 查询，这一步只有上面的缓存用不到才会执行到 */
        data = findAll(parameter.getPageBean(), parameter.getQueryBean());
        if(paging) data.calcDynamicBar();  /* 计算动态显示条 */

        /* 现在得到统计信息 */
        statistics = new Statistics().
                setNowTotal(deviceDao.selectCount(null)).
                setNowUpdated(nrTodayUpdateDevice);

        if(data.getRecords() == null){       /* 查询失败了 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.DEVICE_TASK,
                    Message.HANDLER_FAILURE,
                    "设备任务：DEVICE_LIST失败......"
            );
        }

        /* 成功处理 */
        if(message.isNeedView()){
            /* 如果需要视图，将结果、参数以及统计信息放入缓存，并设置返回的页面 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_DEVICE_PAGES, data);
            jsonRedisTemplate.opsForValue().set(Global.REDIS_SEARCH_DEVICE, parameter.getQueryBean());
            jsonRedisTemplate.opsForValue().set(Global.REDIS_DEVICE_STATISTICS, statistics);
            /* 以及所有可用的分组号 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_DEVICE_GROUPNRS, groupNrs);
            rtData = "/devices";
        } else {
            /* 不需要视图，将结果和统计信息转化为json串放到回复消息中 */
            rtData = gson.toJson(new ListResult<Device>().setPageBean(data).setStatistics(statistics));
            logger.warn("--> " + rtData);
        }
        /* 返回结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.DEVICE_TASK,
                Message.HANDLER_SUCCESS,
                rtData
        );
    }

    /**
     * 处理设备的添加和更新服务调用
     * 注意：设备的添加和更新是不需要页面的，所以成功或失败只会返回一个
     * 包含处理结果的消息json字符串，所以这个调用比较简单。
     * @param message 请求消息
     * @return 包含处理结果的消息
     */
    private Message do_add_or_update(Message message) {
        int rows;
        boolean isInsert = false, noId = false;
        int handleState;
        String returnData;
        int updateType = 0;     /* 更新方式，0：id更新，1：mac更新，2：设备号更新 */

        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);

        /* 解析消息中的设备数据 */
        DevNr devNr = new DevNr();
        try {
            Device device = gson.fromJson(message.getData(), Device.class);
            Device before = null;        /* 更新前的设备信息 */
            if(device.getId() == null) noId = true;
            if(noId){   /* 没给id，现在判断设备号存不存在 */
                if(device.getSdevNr() == null){
                    handleState = Message.HANDLER_FAILURE;
                    returnData = "设备任务：添加一个设备信息，但没给出其设备号，无法继续...";
                    transactionManager.rollback(transStatus);
                    /* 这里直接返回错误结果 */
                    return MessageServiceImpl.TaskFinish(
                            message,
                            (long) MessageServiceImpl.DEVICE_TASK,
                            handleState,
                            returnData
                    );
                } else {

                    /* 现在判断指出的设备信息存不存在，存在则是更新，否则添加检查查询条件，查询优先级：MAC地址/设备号 */
                    if(device.getMac() != null){                /* MAC */
                        updateType = 1;
                        before = deviceDao.selectOne(new QueryWrapper<Device>()
                                .select("id", "group_nr", "dev_nr", "intranet_ip")
                                .eq("mac", device.getMac()));
                        /* 调用这一这函数只是为了设置设备号，所以我们忽略返回值 */
                        getDevice(device.getSdevNr(), devNr);
                    } else if(device.getSdevNr() != null){      /* 设备号 */
                        updateType = 2;
                        before = getDevice(device.getSdevNr(), devNr);
                    }

                    if(before == null){
                        isInsert = true;
                    } else {
                        device.setId(before.getId());
                    }
                }
            } else {        /* id更新 */
                before = deviceDao.selectById(device.getId());
                /* 调用这一这函数只是为了设置设备号，所以我们忽略返回值 */
                getDevice(device.getSdevNr(), devNr);
            }

            /* 执行添加和更新
             * 在那之前设置更新时间为当前时间，不管用户指没指定
             * 还有一些都要做的操作
             */
            device.setUptime(TimeUtils.date2UnixStamp(new Date()));
            device.setAdminFk(message.getSource());     /* 管理员 */
            device.setGroupNr(devNr.getGroupNr());  /* 组号 */
            device.setDevNr(devNr.getDevNr());          /* 数据库设备号 */
            logger.debug(isInsert ? "do_add --> " + device : "do_update --> " + device);
            if(isInsert){
                /* 检查内网IP地址，是否有相同的，计算相同IP设备数量 */
                if(device.getIntranetIp() != null){
                    int nrSameIp = deviceDao.selectCount(new QueryWrapper<Device>()
                            .eq("intranet_ip", device.getIntranetIp())) + 1;
                    device.setNrSameIp(nrSameIp);
                    /* 其他相同的设备也需要更新 */
                    if(nrSameIp > 1){
                        deviceDao.update(new Device().setNrSameIp(nrSameIp),
                                new UpdateWrapper<Device>()
                                        .eq("intranet_ip", device.getIntranetIp()));
                    }
                }
                /* 添加 */
                rows = save(device);
            } else {
//                /* 更新之前，让我们康康更改的设备号对应的设备数据是否已经存在
//                 * 存在：正常更新
//                 * 不存在：删除它并连其以前产生的用户数据一起删除，然后再插入新的数据
//                 */
//                Device existDevice;
//                if((existDevice = deviceDao.selectOne(new QueryWrapper<Device>()
//                        .select("id")
//                        .eq("group_nr", devNr.getGroupNr())
//                        .eq("dev_nr", devNr.getDevNr()))) != null){ /* 不存在 */
//                    do_remove(message.setData(existDevice.getId().toString()));
//                    rows = save(device);
//                } else {                                                    /* 存在 */
//                    /* 更新设备信息 */
//                    rows = update(device);
//                }
                /* 需要先知道是不是内网IP是不是修改了，如果被修改了，需要更新旧的IP的所有相同IP设备<相同IP数量>这个字段，减1即可 */
                if(!before.getIntranetIp().equals(device.getIntranetIp())){
                    int nrSameIp = deviceDao.selectCount(new QueryWrapper<Device>()
                            .eq("intranet_ip", before.getIntranetIp())) - 1;
                    deviceDao.update(new Device().setNrSameIp(nrSameIp),
                            new UpdateWrapper<Device>()
                                    .eq("intranet_ip", before.getIntranetIp()));
                }
                /* 更新设备信息 */
                rows = update(device);
                /* 然后，让我们康康是否是该设备的设备号改动了，如果是，需要联动删除引用该旧设备的所有游戏任务
                 */
                if(updateType != 2) {                /* 不是通过设备号更新 才可能出现设备号被更新的情况 */

                    if(!StringUtils.isEmpty(device.getSdevNr()) && !device.getSdevNr().equals(before.getGroupNr() + before.getDevNr())){
                        /* 旧设备已经没有了，相应的游戏任务也没有了，删 */
                        List<GameTask> tasks = gameTaskDao.selectList(new QueryWrapper<GameTask>()
                                .select("user_data_fk")
                                .eq("group_nr", before.getGroupNr())
                                .eq("dev_nr", before.getDevNr()));
                        gameTaskDao.delete(new UpdateWrapper<GameTask>()
                                .eq("group_nr", before.getGroupNr())
                                .eq("dev_nr", before.getDevNr()));
                        /* 现在还需要删除该设备产生的所有用户数据 */
                        for (GameTask task:
                                tasks) {
                            userDataDao.deleteById(task.getUserDataFK());
                        }
                        /* 游戏任务被修改，那么游戏数据和用户数据就是被更新过了 */
                        GameDataTaskImpl.DbIsUpdate = true;
                        UserDataTaskImpl.DbIsUpdate = true;
                    }
                }
            }

            if(rows > 0){   /* 处理成功 */
                updateStatistics();     /* 更新设备统计 */
                handleState = Message.HANDLER_SUCCESS;
                returnData = isInsert ? "设备任务：添加设备信息成功！" : "设备任务：更新设备信息成功！";
            } else {        /* 处理失败 */
                handleState = Message.HANDLER_FAILURE;
                returnData = isInsert ? "设备任务：添加设备信息失败，请重试..." : "设备任务：更新设备信息失败，请重试...";
            }

            /* 提交事务 */
            transactionManager.commit(transStatus);

        } catch (Exception e){  /* 解析失败 */
            /* 如果出现异常，回滚事务，所有操作取消 */
            e.printStackTrace();
            System.out.println();
            transactionManager.rollback(transStatus);
            handleState = Message.HANDLER_FAILURE;
            returnData = "设备任务：添加或更新的设备信息不正确...";
        }

        /* 处理成功，更新设备统计信息 */
        if(handleState == Message.HANDLER_SUCCESS){
            /* 更新设备统计 */
            updateStatistics();
        }

        /* 返回处理结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.DEVICE_TASK,
                handleState,
                returnData
        );
    }

    private Message do_remove(Message message) {
        PageBean<Device> data = null;
        int rows = 0;
        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);

        try {
            Long id = Long.valueOf(message.getData());
            Device before = deviceDao.selectOne(
                    new QueryWrapper<Device>().
                            select("group_nr", "dev_nr")
                            .eq("id", id));
            rows = remove(id);
            if(rows > 0){   /* 删除成功 */
                /* 设备删除了，相应的游戏任务也没有了，删 */
                List<GameTask> tasks = gameTaskDao.selectList(new QueryWrapper<GameTask>()
                        .select("user_data_fk")
                        .eq("group_nr", before.getGroupNr())
                        .eq("dev_nr", before.getDevNr()));
                gameTaskDao.delete(new UpdateWrapper<GameTask>()
                                .eq("group_nr", before.getGroupNr())
                                .eq("dev_nr", before.getDevNr()));
                /* 现在还需要删除该设备产生的所有用户数据 */
                for (GameTask task:
                     tasks) {
                    userDataDao.deleteById(task.getUserDataFK());
                }
                /* 游戏任务被修改，那么游戏数据和用户数据就是被更新过了 */
                GameDataTaskImpl.DbIsUpdate = true;
                UserDataTaskImpl.DbIsUpdate = true;
                /* 更新设备统计 */
                updateStatistics();
                /* 给用户一条通知提示成功提示信息 */
                notifyService.notify(message.getSource(), "设备（" + id + "）删除成功！");
                /* 数据已经更新，得到上次的分页数据和查询数据，模仿调用LIST */
                PageBean<Device> pageBean = (PageBean<Device>) jsonRedisTemplate.opsForValue().get(Global.REDIS_DEVICE_PAGES);
                Device queryBean = (Device) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_DEVICE);
                ListMsgParameter<Device> parameter = new ListMsgParameter();
                parameter.setPageBean(pageBean).setQueryBean(queryBean);
                message.setData(gson.toJson(parameter));
                /* 提交事务 */
                transactionManager.commit(transStatus);
                return do_list(message);
            } else {
                transactionManager.rollback(transStatus);
                return MessageServiceImpl.TaskFinish(
                        message,
                        (long) MessageServiceImpl.DEVICE_TASK,
                        Message.HANDLER_FAILURE,
                        "设备任务：删除失败，这个设备信息可能不存在或已经被移除，请重试..."
                );
            }
        } catch (Exception e){
            /* 如果出现异常，回滚事务，所有操作取消 */
            transactionManager.rollback(transStatus);
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.DEVICE_TASK,
                    Message.HANDLER_FAILURE,
                    "设备任务：删除失败，你未指明任何设备号"
            );
        }
    }

    private Message do_count(Message message) {
        /* 返回处理结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.DEVICE_TASK,
                Message.HANDLER_SUCCESS,
                String.valueOf(deviceDao.selectCount(null))
        );
    }

    private Message do_get(Message message) {
        int handleState;
        String returnData;
        Device device = null;

        /* 解析消息中的设备数据 */
        Long queryDevNr = null;
        try {
            Device queryBean = gson.fromJson(message.getData(), Device.class);
            /* 检查查询条件，优先级：设备id/设备号/MAC地址 */
            if(queryBean.getId() != null){              /* id */
                device = deviceDao.selectById(queryBean.getId());
            } else if(queryBean.getMac() != null){      /* MAC */
                device = deviceDao.selectOne(new QueryWrapper<Device>()
                        .eq("mac", queryBean.getMac()));
            } else if(queryBean.getSdevNr() != null){    /* 设备号 */
                device = getDevice(queryBean.getSdevNr(), null);
            }

            /* 检查查询是否成功 */
            if(device != null){
                /* 处理一下设备信息，用户需要知道的 */
                device.setAdminUsername(userDao.selectUsernameById(device.getAdminFk()));   /* 管理员用户名 */
                device.setSdevNr((device.getGroupNr().equals(noGroupNr) ? "" : device.getGroupNr()) + device.getDevNr());
                handleState = Message.HANDLER_SUCCESS;
                returnData = deviceGson.toJson(device);
            } else {
                handleState = Message.HANDLER_FAILURE;
                returnData = "设备任务：没有找到这条设备的信息，请先通过/device/save_or_update接口上传...";
            }
        } catch (Exception e){  /* 解析失败 */
            handleState = Message.HANDLER_FAILURE;
            returnData = "设备任务：给出的查询条件错误，得到一条设备信息，可以通过指定：设备id/设备号/MAC地址，" +
                    "如果给出多个条件，则优先级关系：设备id/设备号/MAC地址...";
        }

        /* 返回处理结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.DEVICE_TASK,
                handleState,
                returnData
        );
    }

    /**
     * 查询一个设备通过一个用户设备号（用户看见的），包含分组号和数据库设备号
     */
    public Device getDevice(String sdevNr, DevNr devNr){
        Device device = null;
        /* 检查设备号【提供给用户的】 */
        String groupNr = sdevNr.substring(0, 1);
        /* 需要分组吗？ */
        boolean isGroup = false;
        for (String s :
                groupNrs) {
            if(s.equals(groupNr)){
                isGroup = true;
            }
        }
        Long queryDevNr;
        if(noGroupNr.equals(groupNr)){  /* 显式不分组 */
            logger.debug(" --> 显式不分组");
            groupNr = noGroupNr;
            queryDevNr = Long.valueOf(sdevNr.substring(1));
            device = deviceDao.selectOne(new QueryWrapper<Device>()
                    .eq("dev_nr", queryDevNr)
                    .eq("group_nr", groupNr));
        } else if(!isGroup) {           /* 隐式不分组 */
            logger.debug(" --> 隐式不分组");
            groupNr = noGroupNr;
            queryDevNr = Long.valueOf(sdevNr);
            device = deviceDao.selectOne(new QueryWrapper<Device>()
                    .eq("dev_nr", queryDevNr)
                    .eq("group_nr", groupNr));
        } else {                        /* 分组 */
            logger.debug(" --> 分组");
            queryDevNr = Long.valueOf(sdevNr.substring(1));
            device = deviceDao.selectOne(new QueryWrapper<Device>()
                    .eq("dev_nr", queryDevNr)
                    .eq("group_nr", groupNr));
        }

        /* 设置设备号 */
        if(devNr != null){
            devNr.setGroupNr(groupNr);
            devNr.setDevNr(queryDevNr);
        }

        /* 如果没查到，将原来的查询设备号放入device中传回 */
        return device;
    }

    public PageBean<Device> findAll(PageBean<Device> pageBean, Device queryBean) {
        QueryWrapper<Device> queryWrapper = null;
        DbIsUpdate = false;

        /* 封装条件 */
        if(queryBean != null){
            queryWrapper = new QueryWrapper<>();
            if(queryBean.getGroupNr() != null && !queryBean.getGroupNr().equals(noGroupNr)){    /* 按分组号查询 */
                queryWrapper.eq("group_nr", queryBean.getGroupNr());
            }
            if(queryBean.getDevNr() != null){       /* 设备编号开始 */
                queryWrapper.ge("dev_nr", queryBean.getDevNr());
            }
            if(queryBean.getDevNrEnd() != null){    /* 设备编号结束 */
                queryWrapper.le("dev_nr", queryBean.getDevNrEnd());
            }
            if(queryBean.getName() != null){        /* 设备名称 */
                queryWrapper.like("name", queryBean.getName());
            }
            if(queryBean.getSysVersion() != null){  /* 系统版本号 */
                queryWrapper.like("sys_version", queryBean.getSysVersion());
            }
            if(queryBean.getMac() != null){         /* 设备MAC地址 */
                queryWrapper.like("mac", queryBean.getMac());
            }
            if(queryBean.getIntranetIp() != null){  /* 内网IP */
                queryWrapper.like("intranet_ip", queryBean.getIntranetIp());
            }
            if(queryBean.getExtranetIp() != null){  /* 外网IP */
                queryWrapper.like("extranet_ip", queryBean.getExtranetIp());
            }
        }

        /* 好了，可以从数据库中开始查询了 */
        if(pageBean == null){
            pageBean = new PageBean<>();
            pageBean.setRecords(deviceDao.selectList(queryWrapper));
        } else {
            pageBean = deviceDao.selectPage(pageBean, queryWrapper);
        }

        /* 获得设备的管理员并设置用户模式的设备号 */
        for(Device dev : pageBean.getRecords()){
            dev.setAdmin(new User().setUsername(userDao.selectUsernameById(dev.getAdminFk())));
            dev.setSdevNr((dev.getGroupNr().equals(noGroupNr) ? "" : dev.getGroupNr()) + dev.getDevNr());
        }

        /* 返回结果 */
        return pageBean;
    }

    /* 不支持该服务调用 */
    private Message do_no_call(Message message) {
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.DEVICE_TASK,
                Message.HANDLER_FAILURE,
                "设备任务：你请求了一个不存在的功能服务......"
        );
    }

    @Override
    public int saveOrUpdate(Device record) {
        DbIsUpdate = true;
        int rows = 0;
        // 根据是否有id决定是添加还是更新
        if(record.getId() == null){
            rows = save(record);
        } else {
            rows = update(record);
        }
        return rows;
    }

    @Override
    @CacheEvict(key = "#id")
    public int remove(Long id) {
        DbIsUpdate = true;
        return deviceDao.deleteById(id);
    }

    @Override
    public int save(Device record) {
        DbIsUpdate = true;
        return deviceDao.insert(record);
    }

    @Override
    @Cacheable(
            key = "#id",                               /* 缓存key使用用户的id */
            cacheManager = "jsonRedisCacheManager",     /* 使用json缓存管理器 */
            unless = "#result == null"                  /* 当没查到时，不缓存 */
    )
    public Device findById(Long id) {
        return deviceDao.selectById(id);
    }

    @Override
    @CachePut(key = "#record.id")
    public int update(Device record) {
        DbIsUpdate = true;
        return deviceDao.updateById(record);
    }

    /* 今日更新设备数量 */
    public static Map<Integer, Integer> WeekNrUpdateDevice = new HashMap<>();
    /* 现在是星期几 */
    private static int NowWeekDay = TimeUtils.getDayOfWeek();
    /* 初始化设备统计功能 */
    static {
        System.out.println(" --> 设备统计功能初始化完毕 <-- ");
        /* 初始化所有星期设备更新数量为0 */
        for (int i = 1; i <= 7; i++){
            WeekNrUpdateDevice.put(i, 0);
        }
    }
    /* 今日更新设备数量 */
    private int nrTodayUpdateDevice = 0;
    private void updateStatistics(){
        /* 现在是星期几? */
        int nowWeekDay = TimeUtils.getDayOfWeek();
        if(nowWeekDay != NowWeekDay){   /* 如果星期几变了，说明到了第二天 */
            nrTodayUpdateDevice = 0;    /* 今日更新数量重置为0 */
        } else {                        /* 日期没变 */
            nrTodayUpdateDevice++;
        }
        /* 最后，更新周统计映射 */
        WeekNrUpdateDevice.put(nowWeekDay, nrTodayUpdateDevice);
    }

}
